/*
 * Copyright 2011 Kim Lindhardt Madsen
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package dk.lindhardt.gwt.geie.shared;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * User: AnAmuser
 * Date: 17-09-11
 * <p/>
 * Keeps track of the correction number applied to every single position in the table. This is only the case when
 * positioning type on the table is HTML. The correction should be applied to positions when working in html positioning.
 */
public class ColumnCorrectionBookKeeper implements Serializable {

   /**
    * A grid of column corrections from excel to html
    */
   private Map<Integer, HashMap<Integer, Integer>> excel2HTMLCorrections;

   /**
    * A grid of column corrections from html to excel
    */
   private Map<Integer, HashMap<Integer, Integer>> html2ExcelCorrections;

   /**
    * A book keeper of the column corrections applied to the cells in a a table
    */
   ColumnCorrectionBookKeeper() {
      excel2HTMLCorrections = new HashMap<Integer, HashMap<Integer, Integer>>();
      html2ExcelCorrections = new HashMap<Integer, HashMap<Integer, Integer>>();
   }

   /**
    * Register position in this book keeper
    * @param position the position to register
    */
   void register(Position position) {
      int row = position.getRow();
      int column = position.getColumn();
      if (position.getSpanRow() > 1 && position.getSpanColumn() <= 1) {
         for (int r = 1; r < position.getSpanRow(); r++) {
            addToCorrection(row + r, column + 1, 1, true);
         }
      } else if (position.getSpanColumn() > 1 && position.getSpanRow() <= 1) {
         addToCorrection(row, column + position.getSpanColumn(), position.getSpanColumn() - 1, true);
      } else if (position.getSpanColumn() > 1 && position.getSpanRow() > 1) {
         addToCorrection(row, column + position.getSpanColumn(), position.getSpanColumn() - 1, true);
         for (int r = 1; r < position.getSpanRow(); r++) {
            addToCorrection(row + r, column + position.getSpanColumn(), position.getSpanColumn(), true);
         }
      }
   }

   /**
    * Unregister a position in this book keeper.
    * @param position the position to unregister
    */
   void unRegister(Position position) {
      int row = position.getRow();
      int column = position.getColumn();
      if (position.getSpanRow() > 1 && position.getSpanColumn() <= 1) {
         for (int r = 1; r < position.getSpanRow(); r++) {
            addToCorrection(row + r, column + 1, -1, true);
         }
      } else if (position.getSpanColumn() > 1 && position.getSpanRow() <= 1) {
         addToCorrection(row, column + position.getSpanColumn(), -position.getSpanColumn() + 1, true);
      } else if (position.getSpanColumn() > 1 && position.getSpanRow() > 1) {
         addToCorrection(row, column + position.getSpanColumn(), position.getSpanColumn() - 1, true);
         for (int r = 1; r < position.getSpanRow(); r++) {
            addToCorrection( row + r, column + position.getSpanColumn(), -position.getSpanColumn(), true);
         }
      }
   }

   /**
    * Adds the correction at the given position and if firstCall is true to all positions right of the given position
    */
   private void addToCorrection(int row, int column, int correction,  boolean firstCall) {
      int oldCorrection = getColumnCorrection(row, column, PositioningType.EXCEL);
      addToCorrection(row, column, correction, firstCall, oldCorrection, excel2HTMLCorrections);
      addToCorrection(row, column - correction - oldCorrection, correction, firstCall, oldCorrection, html2ExcelCorrections);
   }

   private void addToCorrection(
         int row,
         int column,
         int correction,
         boolean firstCall,
         Integer oldCorrection,
         Map<Integer, HashMap<Integer, Integer>> map) {
      if (oldCorrection != null) {
         int newCorrection = oldCorrection + correction;
         setColumnCorrection(row, column, newCorrection, map);
         if (firstCall) {
            HashMap<Integer, Integer> columnMap = map.get(row);
            for (Integer c : columnMap.keySet()) {
               if (c > column) {
                  int oldCor = getColumnCorrection(row, column, PositioningType.EXCEL);
                  addToCorrection(row, c, correction, false, oldCor, map);
               }
            }
         }
      } else {
         setColumnCorrection(row, column, correction, map);
      }
   }

   /**
    * Sets the correction at the given position
    */
   private void setColumnCorrection(
         int row, int column, int columnCorrection, Map<Integer, HashMap<Integer, Integer>> map) {
      HashMap<Integer, Integer> columnMap = map.get(row);
      if (columnMap == null) {
         columnMap = new HashMap<Integer, Integer>();
         map.put(row, columnMap);
      }
      columnMap.put(column, columnCorrection);
   }

   /**
    * Gets the column correction that is to be applied to the given position.
    * @param row the row of the position
    * @param column the column of the position
    * @param type the type of the given positions
    * @return the column correction
    */
   public int getColumnCorrection(int row, int column, PositioningType type) {
      Map<Integer, HashMap<Integer, Integer>> map;
      if (PositioningType.EXCEL.equals(type)) {
         map = excel2HTMLCorrections;
      } else {
         map = html2ExcelCorrections;
      }

      HashMap<Integer, Integer> columnMap = map.get(row);
      if (columnMap == null) {
         columnMap = new HashMap<Integer, Integer>();
         map.put(row, columnMap);
      }
      Integer columnCorrection = columnMap.get(column);
      if (columnCorrection == null) {
         for (int c = column - 1; c >= 0; c--) {
            columnCorrection = columnMap.get(c);
            if (columnCorrection != null) {
               return columnCorrection;
            }
         }
         return 0;
      } else {
         return columnCorrection;
      }
   }

   /**
    * Convenience method of {@link #getColumnCorrection(int, int, PositioningType)}
    * @param position a position
    * @param type type of the position
    * @return the column correction applied to this position
    */
   public int getColumnCorrection(Position position, PositioningType type) {
      return getColumnCorrection(position.getRow(), position.getColumn(), type);
   }

   /**
    * Converts a position to the given type
    * @param position the position
    * @param type the type
    * @return a position where correction has been applied
    */
   public Position applyCorrection(Position position, PositioningType type) {
      Position result;
      if (PositioningType.EXCEL.equals(type)) {
         result = new Position(
               position.getRow(), position.getColumn() + getColumnCorrection(position, PositioningType.HTML));
      } else {
         result = new Position(
               position.getRow(), position.getColumn() - getColumnCorrection(position, PositioningType.EXCEL));
      }
      result.setSpanColumn(position.getSpanColumn());
      result.setSpanRow(position.getSpanRow());
      return result;
   }
}
